/**
 * 从目标对象获取某些属性的值
 * @param target 目标对象
 * @param pickKeys 选择的对象的键的数组
 */
export const pick = <T extends Record<string, any>, K extends keyof T>(
    target: T,
    pickKeys: K[],
): Pick<T, K> => {
    if (target === undefined) return {} as Pick<T, K>

    let result = {} as T

    pickKeys.forEach(key => target.hasOwnProperty(key) && (result[key] = target[key]))
    return result
}

/**
 * 返回一个值的空状态
 * @param {any} val 任意值
 * @param {boolean} isStrict 是否为严格检测，就是只有null和undefined为空。
 */
export function isEmpty(val: any, isStrict = false) {
    if (isStrict) {
        return val === null || val === undefined
    }

    const type = Object.prototype.toString.call(val).slice(8, -1).toLowerCase()
    switch (type) {
        case 'array':
            return val.length === 0
        case 'object':
            return !Object.keys(val).length
        default:
            return !val
    }
}

/** 设置值的小数的精确度 */
export const toDecimalPrecision = (val: any, precision: number) => {
    // 空值过滤
    if (val === undefined || val === null || val === '') return 0
    const valByNumber = Number(val)

    // 不合法的数字过滤
    if (!Number.isSafeInteger(parseInt(String(valByNumber)))) return 0

    return parseFloat(valByNumber.toFixed(precision))
}

/**
 * 排除一个对象的某些键和值
 * @param target 目标对象
 * @param omitKeys 排除的对象的键的数组
 */
export const omit = <T extends Record<string, any>, K extends keyof T>(
    target: T,
    omitKeys: K[],
): Omit<T, K> => {
    if (target === undefined) return {} as Omit<T, K>

    const result = { ...target }

    omitKeys.forEach(key => {
        delete result[key]
    })
    return result
}

/**
 * 把rpx转换成根据屏幕计算的同比例px
 * @param {number} rpx
 * @returns {number} px
 */
export function rpxToPx(rpx: number, params?: { screenWidth?: number; base?: number }) {
    const { screenWidth = screen.width, base = 750 } = params ?? {}

    if (typeof rpx !== 'number')
        throw new Error(`rpxTransToPx参数只能传number类型, 实际却传入了:${typeof rpx}`)

    function core(v: number) {
        return (screenWidth / base) * v
    }

    return core(rpx)
}

/**
 * 把px转换成根据屏幕计算的同比例rpx
 * @param {number} px
 * @returns {number} rpx
 */
export function pxToRpx(px: number, params?: { screenWidth?: number; base?: number }) {
    const { screenWidth = screen.width, base = 750 } = params ?? {}

    if (typeof px !== 'number')
        throw new Error(`pxTransToRpx参数只能传number类型, 实际却传入了:${typeof px}`)

    function core(v: number) {
        return (screenWidth / base + 1) * v
    }

    return core(px)
}

/**
 * 判断v值的类型，具有·类型收窄·功能
 * @param v 任意值
 */
export const asserts = (() => {
    const isObject = (v: any): v is object => v instanceof Object
    const isString = (v: any): v is string => typeof v === 'string'
    const isNumber = (v: any): v is number => typeof v === 'number'
    const isBoolean = (v: any): v is boolean => typeof v === 'boolean'
    const isBigint = (v: any): v is bigint => typeof v === 'bigint'
    const isSymbol = (v: any): v is symbol => typeof v === 'symbol'

    const isArray = <T extends any[]>(v: any): v is T => v instanceof Array
    const isBlob = (v: any): v is Blob => v instanceof Blob
    const isDate = (v: any): v is Date => v instanceof Date
    const isFile = (v: any): v is File => v instanceof File
    const isFormdata = (v: any): v is FormData => v instanceof FormData
    const isPromise = <T extends any>(v: any): v is Promise<T> => v instanceof Promise
    const isFunction = <T extends (...args: any) => any>(v: any): v is T => v instanceof Function

    const isUndefined = (v: any): v is undefined => v === undefined
    const isNull = (v: any): v is null => v === null

    return {
        isObject,
        isString,
        isNumber,
        isBoolean,
        isUndefined,
        isFunction,
        isBigint,
        isSymbol,
        isNull,
        isArray,
        isBlob,
        isDate,
        isFile,
        isFormdata,
        isPromise,
    }
})()

/** 判断是否是空的{@link React.ReactNode} */
export const isEmptyNode = (node: React.ReactNode) => {
    if (asserts.isArray(node)) {
        return node.length === 0
    } else if (asserts.isNull(node) || asserts.isUndefined(node)) {
        return true
    } else if (asserts.isBoolean(node)) {
        return true
    } else if (asserts.isObject(node)) {
        return false
    }
    return false
}
